跳到主要内容

使用 Go 自签发证书

数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。证书签发涉及到了非对称加密方面的知识,这里介绍使用 golang中 的 x509 标准库进行证书自签发,还有证书签发后如何使用 golang 进行双向认证。

生成证书

生成根证书

根证书是 CA 认证中心给自己颁发的证书,是信任链的起始点。这里我们自己做 CA 使用 openssl 命令来生成根证书。

# Generate CA private key (制作ca.key 私钥)
openssl genrsa -out ca.key 2048

# Generate CSR
openssl req -new -key ca.key -out ca.csr

# Generate Self Signed certificate(CA 根证书)
openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

开始生成 X509 格式的自签名证书,会要求输入区别名 DN 的各项信息(国家,城市,组织,姓名,email 等

整个提示将如下所示:

OutputCountry Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Guangdong
Locality Name (eg, city) []:Shenzhen
Organization Name (eg, company) [Internet Widgits Pty Ltd]:alsritter, Inc.
Organizational Unit Name (eg, section) []:R&D Department
Common Name (e.g. server FQDN or YOUR name) []:alsritter CA ROOT
Email Address []:alsritter@outlook.com

到这里根证书就制作好了,下面开始使用 golang 根据根证书签发下一级证书。

加载根证书

golang 的 x509 标准库下有个 Certificate 结构,这个结构就是证书解析后对应的实体。新证书需要先生成秘钥对,然后使用根证书的私钥进行签名

首先加载根证书和私钥

func LoadRootCertificate(rootCa, rootKey string) (*x509.Certificate, *rsa.PrivateKey, error) {
/** 首先读取根证书的证书和私钥 **/
//解析根证书
caFile, err := ioutil.ReadFile(rootCa)
if err != nil {
return nil, nil, err
}

caBlock, _ := pem.Decode(caFile)
rootCert, err := x509.ParseCertificate(caBlock.Bytes)

if err != nil {
return nil, nil, err
}

//解析私钥
keyFile, err := ioutil.ReadFile(rootKey)
if err != nil {
return nil, nil, err
}
keyBlock, _ := pem.Decode(keyFile)
praKey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
return nil, nil, err
}

return rootCert, praKey, nil
}

生成证书模板

然后需要生成新证书的模板,里面的字段根据自己需求填写。

import (
"crypto/rand"
random "math/rand"
)

func GenerateCertTemplate() *x509.Certificate {
/** 然后需要生成新证书的模板,里面的字段根据自己需求填写 **/
domain := "github.com"
rd := random.New(random.NewSource(time.Now().UnixNano()))
cer := &x509.Certificate{
SerialNumber: big.NewInt(rd.Int63()), //证书序列号
Subject: pkix.Name{
Country: []string{"CN"},
Organization: []string{"Easy"},
OrganizationalUnit: []string{"Easy"},
Province: []string{"ShenZhen"},
CommonName: domain,
Locality: []string{"ShenZhen"},
},
NotBefore: time.Now(), //证书有效期开始时间
NotAfter: time.Now().AddDate(1, 0, 0), //证书有效期结束时间
BasicConstraintsValid: true, //基本的有效性约束
IsCA: false, //是否是根证书
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, //证书用途(客户端认证,数据加密)
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
EmailAddresses: []string{"alsritter@outlook.com"},
IPAddresses: []net.IP{net.ParseIP("192.168.20.1")}, // 本机内网地址
}
return cer
}

生成新证书

有了上面的信息就可以用于签发客户端证书了

func GenerateCert(rootCert, templateCert *x509.Certificate, rootKey *rsa.PrivateKey) ([]byte, []byte, error) {
//生成公钥私钥对
priKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
ca, err := x509.CreateCertificate(rand.Reader, templateCert, rootCert, &priKey.PublicKey, rootKey)
if err != nil {
return nil, nil, err
}

//编码证书文件和私钥文件
caPem := &pem.Block{
Type: "CERTIFICATE",
Bytes: ca,
}
ca = pem.EncodeToMemory(caPem)
buf := x509.MarshalPKCS1PrivateKey(priKey)
keyPem := &pem.Block{
Type: "PRIVATE KEY",
Bytes: buf,
}
key := pem.EncodeToMemory(keyPem)

return ca, key, nil
}

生成测试

func main() {
rootCert, rootKey, err := gen_cert.LoadRootCertificate("./ca.crt", "./ca.key")
if err != nil {
log.Fatal(err)
}

// 生成模板证书
templateCert := gen_cert.GenerateCertTemplate()

// 签发好的证书和私钥
cert, key, err := gen_cert.GenerateCert(rootCert, templateCert, rootKey)
if err != nil {
log.Fatal(err)
}

err = ioutil.WriteFile("client_cert.pem", cert, fs.ModePerm)
if err != nil {
log.Fatal(err)
}

err = ioutil.WriteFile("client_key.pem", key, fs.ModePerm)
if err != nil {
log.Fatal(err)
}
}

最后生成的文件一览

20220609164505

使用 golang 进行双向认证

客户端

    //加载客户端证书
//这里加载的是服务端签发的
cert, err := tls.LoadX509KeyPair("client_cert.pem", "client_key.pem")
if err != nil {
log.Fatalln(err)
}

config := &tls.Config{
//这里先不验证服务端证书,是自己签发的呀
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
}

raddr, err := net.ResolveTCPAddr("tcp", "192.168.20.1:6001")
if err != nil {
log.Fatalln(err)
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
log.Fatalln(err)
}

tlsConn := tls.Client(conn, config)

tlsConn 就和 net.Conn 一样了,当调用 Wirte 时就会进行握手,如果服务端证书不符合要求,就会返回错误

服务端

    //这里读取的是根证书
buf, err := ioutil.ReadFile(d.conf.Tls.CA)
if err != nil {
return
}

pool := x509.NewCertPool()
pool.AppendCertsFromPEM(buf)

//加载服务端证书
cert, err := tls.LoadX509KeyPair(d.conf.Tls.Cert, d.conf.Tls.Key)
if err != nil {
return
}

tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: pool,
}
//accept到conn后
tlsConn := tls.Server(conn, tlsConfig)

这个 tlsConn 和客户端的一样也可以手动调用 Handshake 进行握手

References